home *** CD-ROM | disk | FTP | other *** search
/ Turnbull China Bikeride / Turnbull China Bikeride - Disc 2.iso / STUTTGART / LANG / C / LIB / OSLIB / EXAMPLES / doc / x
Text File  |  1995-09-06  |  12KB  |  297 lines

  1. The exception library
  2. --- --------- -------
  3.    When writing code in BASIC, we are used to installing error-handlers
  4. to cope with SWI's that generate errors. In C this is less common,
  5. mainly because of the problem of writing nested error-handlers in a
  6. modular way. Nevertheless, there are still advantages to doing so. 
  7.  
  8.    In RISC O S, any system call can return an error indicator, of type
  9. |os_error *|, instead of the results that are expected under normal
  10. termination. If the normally-terminating SWI could be considered to be
  11. of type
  12.  
  13.       Entry -> Exit,
  14.  
  15. where Entry is the type of its entry conditions and Exit the type of its
  16. exit conditions, then the SWI as provided must be considered to be of
  17. type
  18.  
  19.       Entry -> Union (Exit, os_error *).
  20.  
  21. In general, both Entry asnd Exit are compound types: for example, the
  22. type of pdriver_draw_page() is 
  23.  
  24.       Struct (int, os_box *, int, char *, int) -> Struct (bool, int)
  25.  
  26. (which would be rendered into C as void pdriver_draw_page (int, os_box
  27. *, int, char *, bool *, int *). However, this ignores the fact that it
  28. might return an error instead; so the real type is
  29.  
  30.       Struct (int, os_box *, int, char *, int) ->
  31.             Union (Struct (bool, int), os_error *)
  32.  
  33. which is rendered as os_error *xpdriver_draw_page (int, os_box *, int,
  34. char *, bool *, int *).
  35.  
  36.    The problem with using functions of this form is that every single
  37. return value must be checked, and appropriate action taken if an error
  38. has been returned. This leads to code like
  39.  
  40.       if ((error = xpdriver_draw_page (1, &req, page, NULL, &more,
  41.             NULL)) != NULL)
  42.          goto finish;
  43.  
  44. (If you don't like 'goto,' then feel free to imagine there is a call to
  45. an error-handling function there instead. The main argument is not
  46. affected by this.) In effect this "wastes" R0 (the register in which the
  47. result is returned), and the following test, on a condition which hardly
  48. ever occurs.
  49.  
  50.    RISC O S, and the C run-time kernel, provide a mechanism to avoid
  51. this. By calling the non-X form of the SWI, we can ensure that the
  52. normal flow of control of a programme is interrupted when an error
  53. occurs, without having to test for it explicitly. This also means that
  54. the return value of the function can be used to return the most useful
  55. output argument. (Which one this is is a matter of taste, and OSLib
  56. makes this decision in a hopefully-useful way.)
  57.  
  58.    Therefore, the call can be written as
  59.  
  60.       more = pdriver_draw_page (1, &req, page, NULL, NULL);
  61.  
  62. To do this requires the installation of an error handler. The C run-
  63. time system does some of this for you. An exception library, the x_
  64. module, does the rest.
  65.  
  66.    To use the exception library, code is written in this form:
  67.  
  68.    {  x_exception x;
  69.  
  70.       /statements (1)/
  71.  
  72.       x_TRY (&x)
  73.          /statement (2)/
  74.       x_CATCH (&x)
  75.          /statement (3)/
  76.  
  77.       /statements (4)/
  78.    }
  79.  
  80.    The first point is the declaration. To catch an exception locally,
  81. the C run-time system needs some space to store various things. This is
  82. provided in a structure called an |x_exception|. This must be declared
  83. like this---on the stack, not static---or nested error handlers won't
  84. work.
  85.  
  86.    |x_TRY| may be thought of as having a similar syntax to |do|;
  87. |x_CATCH()| to |while|. Both must be present, at the same lexical level,
  88. just as above---if an |x_TRY| is not balanced by an |x_CATCH|, the
  89. effects are highly unpredictable. They must both be provided with
  90. pointers to the same |x_exception| structure. The first statement is
  91. usually a block (in {...}), called the try block, though it could be a
  92. single statement. Exceptions thrown from here cause control to be
  93. transferred to the statement after |x_CATCH()|, called the catch block.
  94. If there are no exceptions, control does not enter the catch block at
  95. all. If any exceptions occur in the statement not in the try block (in
  96. the catch block or outside the |x_TRY ... x_CATCH| construction
  97. altogether), they are just thrown as normal (i e, passed up to enclosing
  98. functions).
  99.  
  100.    After control has passed through |x_TRY()|, |x_RETHROW()| may be used
  101. to propagate the exception further. This would normally be the preferred
  102. course.
  103.  
  104.    This means that within the try block, you are free to put calls to
  105. non-X SWI's. Any SWI error, escape or stack overflow error (any of which
  106. can happen to correct programmes) will be caught and handled locally,
  107. without wasting code on explicit error checking. RISC O S does it for
  108. you!
  109.  
  110.    Normally, any exceptions caught should be rethrown, as in the example
  111. code below, or the code that called you will be unaware of the error
  112. that has occurred. This is an important rule: without it, functions
  113. could end up allocating resources that they would not free, and the
  114. whole purpose of the module would have been subverted. If you think of
  115. the whole calling tree at the moment of the exception, control will pass
  116. to the innermost-nested function that has a catch block; this function
  117. should do whatever tidying up it needs, and then rethrow the exception
  118. to the next enclosing one, and so on until the stack is flat and all
  119. interested functions have had a chance to act.
  120.  
  121.    The stack does not have to be completely flat, however: normally, a
  122. command-line driven programme would just report the error and ask for
  123. another command, and a Wimp-based one would use Wimp_ReportError and
  124. poll again. The application writer has complete flexibility.
  125.  
  126.    Blocks written as above may be nested, either in the try block or in
  127. the catch block, and only functions that need to do tidying-up after
  128. themselves need to use |x_TRY| and |x_CATCH|. Functions that they call
  129. can just "get on with the job:" if they call a SWI, or any other
  130. function, that raises an exception, the exception will just "bubble
  131. back" through all the error handlers, back to the level required by the
  132. application writer.
  133.  
  134.    There are other utilities to round out the module:
  135. |x_THROW_LAST_ERROR| throws the last error detected by the run-time
  136. system. It is normally used after a library call fails. |x_THROW| throws
  137. a given error, in just the same way that the SWI's do (it is just
  138. os_generate_error(), really: either way be used), and x_LOCAL_ERROR
  139. declares one for such use. For example,
  140.  
  141.       if ((file = fopen ("DataFile", "w")) == NULL)
  142.          x_THROW_LAST_ERROR ();
  143.  
  144.       /at this point, we know that |file| is a valid handle/
  145.  
  146. Code to open a file, write to it and close it again would look like
  147. this:
  148.  
  149.       FILE *f;
  150.  
  151.       if ((f = fopen ("DataFile", "w")) == NULL)
  152.          x_THROW_LAST_ERROR ();
  153.  
  154.       x_TRY (&x)
  155.       {  if (fprintf (f, "Hello, World!\n") < 0)
  156.             x_THROW_LAST_ERROR ();
  157.  
  158.          /lots of complicated output to a file, which might go wrong/
  159.       }
  160.       x_CATCH (&x)
  161.          ;
  162.  
  163.       if (fclose (f) == NULL)
  164.          x_THROW_LAST_ERROR ();
  165.       x_RETHROW (&x);
  166.  
  167. Note the structure:
  168.  
  169.       do something that changes the global state (open file, allocate
  170.             memory, etc)
  171.       TRY
  172.          operate on the changed state (write to file, use memory)
  173.       CATCH
  174.          ;
  175.       restore the state (close file, free memory)
  176.       rethrow the error
  177.  
  178. Often, as here, the catch block is empty, since the tidying-up code is
  179. the same in the error case as in the correct case.
  180.  
  181.    x_LOCAL_ERROR() allows a static error block to be constructed: this
  182. can then be thrown as a new error. The effect is exactly the same as if
  183. a SWI was called and threw the error. It should be used at the top level
  184. of a source file. For example, code to check for a text file might look
  185. like this:
  186.  
  187.       x_STATIC_ERROR (Error_Wrong_Type, 1, "File must be of type Text")
  188.  
  189.       fileswitch_object_type obj_type;
  190.       bits file_type;
  191.  
  192.       if ((obj_type = osfile_read_stamped_no_path ("Fred", NULL, NULL,
  193.             NULL, NULL, &file_type)) != fileswitch_IS_FILE)
  194.          osfile_make_error ("Fred", obj_type);
  195.  
  196.       if (file_type != osfile_TYPE_TEXT)
  197.          x_THROW (Error_Wrong_Type);
  198.  
  199.       /... if we are here, we have a text file. Conversely, if the file
  200.          was not a text file, the right error has been thrown back to
  201.          the caller .../
  202.  
  203.    There is also x_GLOBAL_ERROR, which does the same job but without the
  204. 'static': it should be used where the error blocks that a source file
  205. uses are part of its interface. In this case, the identifier should also
  206. be declared as an |os_error *| in the source file's accompanying header
  207. file.
  208.  
  209.    There are also functions x_EXIT(), which exits a programme, printing
  210. an error message to |stderr| if one has occurred, and x_REPORT_EXIT()
  211. which exits a programme, calling wimp_report_error() if an error has
  212. occurred. These should only be used in the scope of the |x_exception|
  213. structure|, after a use of |x_TRY|.
  214.  
  215.    There are also functions |x_alloc|, |x_calloc|, |x_free| and
  216. |x_realloc| which are like |malloc|, |calloc|, |free| and |realloc|, but
  217. throw an exception on failure. (It's unfortunate that the relationship
  218. between |x_malloc| and |malloc| is the opposite of that between (say)
  219. |xosmodule_alloc| and |osmodule_alloc|, but it's a logical consequence
  220. of the module naming convention.)
  221.  
  222. Conclusion
  223. ----------
  224.    Any SWI can in principal generate an error at any time, for any
  225. reason. These errors are usually associated with the exhaustion of some
  226. resource (memory, file handles, disc space, etc), and code that checks
  227. error-returning SWI's, and in turn returns the error pointer back to its
  228. caller, is time-consuming and inefficient. Using a handler, and error-
  229. generating SWI's, saves 1 or 2 instructions per SWI veneer (the veneer
  230. doesn't have the 'BVS %99; MOV R, #0' or 'MOVVC R, #0'); and typically 2
  231. at each function call ('CMP R0, #0; BNE R0, finish' in the example
  232. above). It also enables more readable code to be produced. For example,
  233. if you check all return codes, a printing loop might look like this:
  234.  
  235.       if ((error = xpdriver_draw_page (1, &req, page, NULL, &more,
  236.             NULL)) != NULL)
  237.          goto finish;
  238.  
  239.       while (more)
  240.       {  if ((error = xcolourtrans_set_gcol (os_COLOUR_BLACK,
  241.                colourtrans_SET_FG, os_ACTION_OVERWRITE, NULL, NULL))
  242.                != NULL)
  243.             goto finish;
  244.  
  245.          if ((error = xfont_paint (0, t, NONE, 0, 0, NULL, NULL, 0))
  246.                != NULL)
  247.             goto finish;
  248.  
  249.          if ((error = xpdriver_get_rectangle (&req, &more, NULL)) !=
  250.                NULL)
  251.             goto finish;
  252.       }
  253.  
  254.    finish:
  255.  
  256. whereas if you can call x-clear SWI's, you can write instead:
  257.  
  258.       for (more = pdriver_draw_page (1, &req, page, NULL, NULL);
  259.             more; more = pdriver_get_rectangle (&req, NULL))
  260.       {  colourtrans_set_gcol (os_COLOUR_BLACK, colourtrans_SET_FG,
  261.                os_ACTION_OVERWRITE, NULL, NULL);
  262.  
  263.          font_paint (0, t, NONE, 0, 0, NULL, NULL, 0);
  264.       }
  265.  
  266. The first example above compiles to 52 instructions; the second to 44,
  267. for a saving of an enormous 20% in code size (and knock-on effects for
  268. callers). This could also be expected to translate into a 20% speed-up
  269. as well, since all the extra instructions have to be executed. The code
  270. is also substantially easier to write, understand and modify.
  271.  
  272.    The x_ module does not interfere in any way with errors that are not
  273. expected: namely, abort, arithmetic exception, illegal instruction, bad
  274. memory access, termination request. These should probably only be
  275. handled at the very top level in order to provide a last-ditch error
  276. handler: it could do something like saving all unsaved data into a scrap
  277. file.
  278.  
  279.    The C library throws an escape condition when the user presses
  280. Escape, but this behaviour is normally suppressed by the Wimp, except in
  281. special cases like printing. By enabling and disabling escapes around
  282. Wimp_Poll, an application could be easily written to abort its current
  283. operation on Escape and return to its main polling loop, with very
  284. little special code needed, and no risk of files being left open, etc.
  285.  
  286.    The OSLib SWI veneers provide both X and non-X forms of each SWI
  287. function. When writing an application, or a library to be used by
  288. others, there is a big decision to make: do its functions return
  289. |os_error *|, and call X-form SWI's, or do they return a useful result
  290. and call non-X-form SWI's (with error-handlers in the right places)? In
  291. other words, do the library functions mimic x-set SWI's, or x-clear
  292. SWI's?
  293.  
  294.    The libraries and modules described enable us to write better
  295. applications in less time, that run faster and require less memory to
  296. run in. 
  297.